// Copyright 2017  Jos van den Oever <jos@vandenoever.info>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License or (at your option) version 3 or any later version
// accepted by the membership of KDE e.V. (or its successor approved
// by the membership of KDE e.V.), which shall act as a proxy
// defined in Section 14 of version 3 of the license.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

use interface::*;
use std::fs::*;
use std::fs::read_dir;
use std::path::PathBuf;
use std::ffi::OsString;
use std::default::Default;
use std::thread;
use std::sync::mpsc::{Receiver, Sender, channel};
use std::marker::Sync;

pub struct DirEntry {
    name: OsString,
    metadata: Option<Metadata>,
    path: Option<PathBuf>,
    icon: Vec<u8>,
}

impl Item for DirEntry {
    fn new(name: &str) -> DirEntry {
        DirEntry {
            name: OsString::from(name),
            metadata: metadata(name).ok(),
            path: None,
            icon: Vec::new(),
        }
    }
    fn can_fetch_more(&self) -> bool {
        self.metadata.as_ref().map_or(false, |m| m.is_dir())
    }
    fn file_name(&self) -> String {
        self.name.to_string_lossy().to_string()
    }
    fn file_path(&self) -> Option<String> {
        self.path.as_ref().map(|p| p.to_string_lossy().into())
    }
    fn file_permissions(&self) -> i32 {
        42
    }
    fn file_type(&self) -> i32 {
        0
    }
    fn file_size(&self) -> Option<u64> {
        self.metadata.as_ref().map(|m| m.len())
    }
    fn icon(&self) -> &[u8] {
        &self.icon
    }
    fn retrieve(id: usize, parents: Vec<&Self>, outgoing: &Sender<(usize, PathBuf)>) {
        let path: PathBuf = parents.into_iter().map(|e| &e.name).collect();
        if let Err(e) = outgoing.send((id, path)) {
            eprintln!("{}", e);
        }
    }
    fn read_files(
        incoming: Receiver<(usize, PathBuf)>,
        outgoing: Sender<(usize, Vec<Self>)>,
        mut emit: FileSystemTreeEmitter,
    ) {
        thread::spawn(move || while let Ok((id, path)) = incoming.recv() {
            let mut v = Vec::new();
            if let Ok(it) = read_dir(&path) {
                for i in it.filter_map(|v| v.ok()) {
                    let de = DirEntry {
                        name: i.file_name(),
                        metadata: i.metadata().ok(),
                        path: Some(i.path()),
                        icon: Vec::new(),
                    };
                    v.push(de);
                }
            }
            v.sort_by(|a, b| a.name.cmp(&b.name));
            if let Ok(()) = outgoing.send((id, v)) {
                    emit.new_data_ready(Some(id));
                } else {
                    return;
                }
        });
    }
}

impl Default for DirEntry {
    fn default() -> DirEntry {
        DirEntry {
            name: OsString::new(),
            metadata: None,
            path: None,
            icon: Vec::new(),
        }
    }
}

pub trait Item: Default {
    fn new(name: &str) -> Self;
    fn can_fetch_more(&self) -> bool;
    fn retrieve(id: usize, parents: Vec<&Self>, outgoing: &Sender<(usize, PathBuf)>);
    fn read_files(
        incoming: Receiver<(usize, PathBuf)>,
        outgoing: Sender<(usize, Vec<Self>)>,
        emit: FileSystemTreeEmitter,
    );
    fn file_name(&self) -> String;
    fn file_path(&self) -> Option<String>;
    fn file_permissions(&self) -> i32;
    fn file_type(&self) -> i32;
    fn file_size(&self) -> Option<u64>;
    fn icon(&self) -> &[u8];
}

pub type FileSystemTree = RGeneralItemModel<DirEntry>;

struct Entry<T: Item> {
    parent: Option<usize>,
    row: usize,
    children: Option<Vec<usize>>,
    data: T,
}

pub struct RGeneralItemModel<T: Item> {
    emit: FileSystemTreeEmitter,
    model: FileSystemTreeTree,
    entries: Vec<Entry<T>>,
    path: Option<String>,
    outgoing: Sender<(usize, PathBuf)>,
    incoming: Receiver<(usize, Vec<T>)>,
}

impl<T: Item> RGeneralItemModel<T>
where
    T: Sync + Send,
{
    fn reset(&mut self) {
        self.model.begin_reset_model();
        self.entries.clear();
        if let Some(ref path) = self.path {
            let root = Entry {
                parent: None,
                row: 0,
                children: None,
                data: T::new(path),
            };
            self.entries.push(root);
        }
        self.model.end_reset_model();
    }
    fn get(&self, index: usize) -> &Entry<T> {
        &self.entries[index]
    }
    fn retrieve(&self, index: usize) {
        let parents = self.get_parents(index);
        T::retrieve(index, parents, &self.outgoing);
    }
    fn process_incoming(&mut self) {
        while let Ok((id, entries)) = self.incoming.try_recv() {
            if self.entries[id].children.is_some() {
                continue;
            }
            let mut new_entries = Vec::new();
            let mut children = Vec::new();
            for (r, d) in entries.into_iter().enumerate() {
                new_entries.push(Entry {
                    parent: Some(id),
                    row: r,
                    children: None,
                    data: d,
                });
                children.push(self.entries.len() + r);
            }
            if new_entries.is_empty() {
                self.entries[id].children = Some(children);
            } else {
                self.model.begin_insert_rows(
                    Some(id),
                    0,
                    new_entries.len() - 1,
                );
                self.entries[id].children = Some(children);
                self.entries.append(&mut new_entries);
                self.model.end_insert_rows();
            }
        }
    }
    fn get_parents(&self, id: usize) -> Vec<&T> {
        let mut pos = Some(id);
        let mut e = Vec::new();
        while let Some(p) = pos {
            e.push(p);
            pos = self.entries[p].parent;
        }
        e.into_iter().rev().map(|i| &self.entries[i].data).collect()
    }
}

impl<T: Item> FileSystemTreeTrait for RGeneralItemModel<T>
where
    T: Sync + Send,
{
    fn new(mut emit: FileSystemTreeEmitter, model: FileSystemTreeTree) -> Self {
        let (outgoing, thread_incoming) = channel();
        let (thread_outgoing, incoming) = channel::<(usize, Vec<T>)>();
        T::read_files(thread_incoming, thread_outgoing, emit.clone());
        let mut tree: RGeneralItemModel<T> = RGeneralItemModel {
            emit,
            model,
            entries: Vec::new(),
            path: None,
            outgoing,
            incoming,
        };
        tree.reset();
        tree
    }
    fn emit(&mut self) -> &mut FileSystemTreeEmitter {
        &mut self.emit
    }
    fn path(&self) -> Option<&str> {
        self.path.as_ref().map(|s| &s[..])
    }
    fn set_path(&mut self, value: Option<String>) {
        if self.path != value {
            self.path = value;
            self.emit.path_changed();
            self.reset();
        }
    }
    fn can_fetch_more(&self, index: Option<usize>) -> bool {
        let entry = self.get(index.unwrap_or(0));
        entry.children.is_none() && entry.data.can_fetch_more()
    }
    fn fetch_more(&mut self, index: Option<usize>) {
        self.process_incoming();
        if !self.can_fetch_more(index) {
            return;
        }
        self.retrieve(index.unwrap_or(0));
    }
    fn row_count(&self, index: Option<usize>) -> usize {
        if self.entries.is_empty() {
            return 0;
        }
        if let Some(i) = index {
            let entry = self.get(i);
            if let Some(ref children) = entry.children {
                children.len()
            } else {
                self.retrieve(i);
                0
            }
        } else {
            1
        }
    }
    fn index(&self, index: Option<usize>, row: usize) -> usize {
        if let Some(index) = index {
            self.get(index).children.as_ref().unwrap()[row]
        } else {
            0
        }
    }
    fn parent(&self, index: usize) -> Option<usize> {
        self.entries[index].parent
    }
    fn row(&self, index: usize) -> usize {
        self.entries[index].row
    }
    fn check_row(&self, index: usize, _row: usize) -> Option<usize> {
        if index < self.entries.len() {
            Some(self.row(index))
        } else {
            None
        }
    }
    fn file_name(&self, index: usize) -> String {
        self.get(index).data.file_name()
    }
    fn file_permissions(&self, index: usize) -> i32 {
        self.get(index).data.file_permissions()
    }
    #[allow(unused_variables)]
    fn file_icon(&self, index: usize) -> &[u8] {
        self.get(index).data.icon()
    }
    fn file_path(&self, index: usize) -> Option<String> {
        self.get(index).data.file_path()
    }
    fn file_type(&self, index: usize) -> i32 {
        self.get(index).data.file_type()
    }
    fn file_size(&self, index: usize) -> Option<u64> {
        self.get(index).data.file_size()
    }
}
